package se.sll.itintegration.catalogue.gui.service.view.capacity;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import lombok.extern.slf4j.Slf4j;

import se.sll.itintegration.catalogue.gui.data.Capacity;
import se.sll.itintegration.catalogue.gui.service.view.capacity.CapacityViewProduct.Availability;
import se.sll.itintegration.catalogue.gui.service.view.capacity.CapacityViewProduct.Status;
import se.sll.service.catalogue.common.Translator;
import se.sll.service.catalogue.common.property.CatalogProperties;
import se.sll.service.catalogue.common.property.CatalogProperties.NAME;

/**
 *
 * @author Christer Palm, Nogui
 */
@Slf4j
public class CapacityTranslator implements Translator<List<Capacity>, CapacityViewProduct> {

    private static final CapacityTranslator INSTANCE = new CapacityTranslator();

    private CapacityTranslator() {
    }

    @Override
    public CapacityViewProduct translate(List<Capacity> from) {
        return translateTo(from, new CapacityViewProduct());
    }

    /**
     * Adds capacity information from the capacity subsystem to the given
     * CapacityViewProduct. If a capacity is already present in
     * CapacityViewProduct, its value is set. Otherwise it is added to
     * CapacityViewProduct with the active flag set to false.
     *
     * The CapacityViewProducts validTo date is set to the minimum of its active
     * capacities.
     *
     * The transformation from abstract {code, value} form to the more
     * structured presentation form makes this translator quite complex. Waiting
     * time codes are paired into CapacityViewCapacity objects, and other codes
     * are translated into their own fields in CapacityViewProduct.
     *
     * @param from
     * @param to
     * @return
     */
    @Override
    public CapacityViewProduct translateTo(List<Capacity> from, CapacityViewProduct to) {
        log.trace("translateTo");
        List<CapacityViewCapacity> caps = to.getCapacities();
        if (caps == null) {
            caps = new ArrayList<>();
            to.setCapacities(caps);
        }
        Date minValidTo = null;
        if (from != null) {
            for (Capacity c : from) {
                if (CapacityConstants.CODESYSTEM_CAPACITY.equals(c.getCodeSystem())) {
                    String code = c.getCode();
                    String value = c.getValue();
                    switch (code) {
                        case CapacityConstants.CODE_AVAILABLE_CAPACITY:
                            to.setAvailableCrossRegionCapacity(translateAvailability(value));
                            minValidTo = minValidTo(minValidTo, c, to.isAvailableCrossRegionCapacityActive());
                            break;

                        case CapacityConstants.CODE_REFERRAL_INFORMATION:
                            to.setReferralInfo(value);
                            minValidTo = minValidTo(minValidTo, c, to.isAvailableCrossRegionCapacityActive());
                            break;
                    }
                }
            }

            minValidTo = addCapacity(caps, from, minValidTo, CapacityConstants.LONG_DISPLAY_NAME_APPOINTMENT, CapacityConstants.SHORT_DISPLAY_NAME_APPOINTMENT,
                    CapacityConstants.CODE_WAIT_TIME_APPOINTMENT, CapacityConstants.CODE_WAIT_TIME_APPOINTMENT_PRIORITY);
            minValidTo = addCapacity(caps, from, minValidTo, CapacityConstants.LONG_DISPLAY_NAME_EXAMINATION, CapacityConstants.SHORT_DISPLAY_NAME_EXAMINATION,
                    CapacityConstants.CODE_WAIT_TIME_EXAMINATION, CapacityConstants.CODE_WAIT_TIME_EXAMINATION_PRIORITY);
            minValidTo = addCapacity(caps, from, minValidTo, CapacityConstants.LONG_DISPLAY_NAME_TREATMENT, CapacityConstants.SHORT_DISPLAY_NAME_TREATMENT,
                    CapacityConstants.CODE_WAIT_TIME_TREATMENT, CapacityConstants.CODE_WAIT_TIME_TREATMENT_PRIORITY);
        }
        to.setValidTo(minValidTo);
        to.setStatus(getStatus(minValidTo));

        log.trace("minValidTo: " + to.getValidTo());

        return to;
    }

    private Availability translateAvailability(String value) {
        Availability availability;
        if ("true".equals(value)) {
            availability = Availability.YES;
        } else if ("false".equals(value)) {
            availability = Availability.NO;
        } else {
            availability = null;
        }
        return availability;
    }

    private static Status getStatus(Date validTo) {
        Status status = Status.EXPIRED;
        if (validTo != null) {
            Date now = new Date();
            if (!validTo.before(now)) {
                Calendar expiringLimit = Calendar.getInstance();
                expiringLimit.setTime(now);
                int warningDays = Integer.parseInt(CatalogProperties.getProperty(NAME.NOTIFICATION_CAPACITY_EXPIRATION_PERIOD_DAYS));
                expiringLimit.add(Calendar.DATE, warningDays);
                if (validTo.after(expiringLimit.getTime())) {
                    status = Status.OK;
                } else {
                    status = Status.EXPIRING;
                }
            }
        }
        return status;
    }

    private static Date addCapacity(List<CapacityViewCapacity> caps, List<Capacity> capacity, Date minValidTo,
            String longDisplayName, String shortDisplayName, String waitTimeCode, String waitTimePriorityCode) {
        Capacity waitTimeCapacity = null;
        Capacity waitTimePriorityCapacity = null;

        for (Capacity c : capacity) {
            if (CapacityConstants.CODESYSTEM_CAPACITY.equals(c.getCodeSystem())) {
                String code = c.getCode();
                if (waitTimeCode.equals(code)) {
                    waitTimeCapacity = c;
                } else if (waitTimePriorityCode.equals(code)) {
                    waitTimePriorityCapacity = c;
                }
            }
        }

        if (waitTimeCapacity != null || waitTimePriorityCapacity != null) {
            // Find existing view capacity
            CapacityViewCapacity viewCapacity = null;
            for (CapacityViewCapacity cap : caps) {
                if (CapacityConstants.CODESYSTEM_CAPACITY.equals(cap.getCodeSystem())
                        && waitTimeCode.equals(cap.getWaitTimeCode())
                        && waitTimePriorityCode.equals(cap.getWaitTimePriorityCode())) {
                    viewCapacity = cap;
                    minValidTo = minValidTo(minValidTo, waitTimeCapacity, cap.isWaitTimeActive());
                    minValidTo = minValidTo(minValidTo, waitTimePriorityCapacity, cap.isWaitTimePriorityActive());
                }
            }
            // Create new if it didn't exist
            if (viewCapacity == null) {
                viewCapacity = new CapacityViewCapacity();
                viewCapacity.setLongDisplayName(longDisplayName);
                viewCapacity.setShortDisplayName(shortDisplayName);
                viewCapacity.setCodeSystem(CapacityConstants.CODESYSTEM_CAPACITY);
                viewCapacity.setWaitTimeCode(waitTimeCode);
                viewCapacity.setWaitTimeActive(false);
                viewCapacity.setWaitTimePriorityCode(waitTimePriorityCode);
                viewCapacity.setWaitTimePriorityActive(false);
                caps.add(viewCapacity);
            }
            if (waitTimeCapacity != null) {
                viewCapacity.setWaitTime(Integer.parseInt(waitTimeCapacity.getValue()));
            }
            if (waitTimePriorityCapacity != null) {
                viewCapacity.setWaitTimePriority(Integer.parseInt(waitTimePriorityCapacity.getValue()));
            }
        }
        return minValidTo;
    }

    private static Date minValidTo(Date minValidTo, Capacity capacity, boolean isActive) {
        if (isActive && capacity != null) {
            Date validTo = capacity.getValidTo();
            if (validTo != null) {
                if (minValidTo == null || validTo.before(minValidTo)) {
                    minValidTo = validTo;
                }
            }
        }
        return minValidTo;
    }

    public static CapacityTranslator getInstance() {
        return INSTANCE;
    }
}
